package com.wang.tool.util;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;

public class ExceptionUtil {

    private static MethodHandle traceMethodHandle;
    static {
        try {
//            getOurStackTrace vs getStackTrace Reduced copy
            Method method = Throwable.class.getDeclaredMethod("getOurStackTrace");
            method.setAccessible(true);
            traceMethodHandle = MethodHandles.lookup().unreflect(method);
        } catch (Throwable var2) {
            SimpleLogUtil.warn("init traceMethodHandle error, errorType:"+var2.getClass().getName()+", errorMsg:"+var2.getMessage());
        }
    }



    /**
     * Format the error stack trace.
     * eg
     * e.toString()
     * at ...
     * Max 9 loop(Caused by: ...)
     *
     * @param e        The Throwable object.
     * @return Formatted stack trace string.
     */
    public static String formatErrorStackTrace(Throwable e) {
        return formatErrorStackTrace(e, 30, 10);
    }

    /**
     * Format the error stack trace.
     * eg
     * e.toString()
     * at ...
     * Max {maxDepth-1} loop(Caused by: ...)
     *
     * @param e        The Throwable object.
     * @param maxLine  Maximum number of lines for one Throwable
     * @param maxDepth max loop caused
     * @return Formatted stack trace string.
     */
    public static String formatErrorStackTrace(Throwable e, int maxLine, int maxDepth) {
        if (e == null) {
            return "";
        }
        try {
            StringBuilder sb = new StringBuilder(maxLine * 100);
            formatErrorStackTrace(e,null, maxLine, maxDepth, sb, 1);
            return sb.toString();
        } catch (Throwable ignore) {
            final StringWriter sw = new StringWriter();
            e.printStackTrace(new PrintWriter(sw, true));
            return sw.toString();
        }
    }

    private static void formatErrorStackTrace(Throwable e, StackTraceElement[] enclosingTrace, int maxLine, int maxDepth, StringBuilder sb, int depth) {
        if (e == null) {
            return;
        }
        StackTraceElement[] trace = getStackTraceElements(e);
        if (trace != null) {
//            copy from e.printStackTrace();
            int framesInCommon = 0;

            // If enclosingTrace is provided, calculate the frames in common
            if (enclosingTrace != null) {
                int m = trace.length - 1;
                int n = enclosingTrace.length - 1;
                while (m >= 0 && n >= 0 && trace[m].equals(enclosingTrace[n])) {
                    m--;
                    n--;
                }
                framesInCommon = trace.length - 1 - m;
            }
            sb.append(e).append(System.lineSeparator());

            int count = Math.min(maxLine, trace.length - framesInCommon);
            for (int i = 0; i < count; i++) {
                sb.append("\tat ").append(trace[i]).append(System.lineSeparator());
            }

            if (framesInCommon != 0 && count < trace.length) {
                sb.append("\t... ").append(framesInCommon).append(" more").append(System.lineSeparator());
            }
        }
        Throwable cause = e.getCause();
        if (cause != null && cause != e) {
            sb.append("Caused by: ");
            if(depth >= maxDepth){
                sb.append(e).append(System.lineSeparator()).append("[Maximum depth reached]");
                return;
            }
            // Pass depth to prevent deep recursion
            formatErrorStackTrace(cause, trace, maxLine, maxDepth, sb, depth + 1);
        }
    }


    /**
     * fetch ori error stackTrace don't change ret content
     * @return
     */
    private static StackTraceElement[] getStackTraceElements(Throwable e) {
        if (e == null) {
            return null;
        }
        if (traceMethodHandle == null) {
            return e.getStackTrace();
        }
        try {
            return (StackTraceElement[]) traceMethodHandle.invoke(e);
        } catch (Throwable var2) {
            return e.getStackTrace();
        }
    }
}
